/*
 * Funambol is a mobile platform developed by Funambol, Inc. 
 * Copyright (C) 2008 Funambol, Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission 
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE 
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License 
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 * 
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite 
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably 
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <CoreFoundation/CFString.h>
#import <AddressBook/AddressBook.h>

#import "PersonDataConverter.h"

#include "vocl/VConverter.h"
#include "stringUtils.h"
#include "spds/spdsutils.h"
#include "base/base64.h"
#include "customs.h"
#include "base/Log.h"


using namespace Funambol;

const CFStringRef kABPersonPhoneFAXLabel     = CFSTR("fax");
const CFStringRef kABPersonPhoneCompanyLabel = CFSTR("company");
const CFStringRef kABPersonPhoneCarLabel     = CFSTR("car");

@implementation PersonDataConverter

- (void)  convertToSyncItem:

                (CFTypeRef)     person
   syncItem:    (SyncItem*)     item
{
    if (version3)
    {
        item->setDataType(TEXT("text/x-vcard"));
    }
    else
    {
        item->setDataType(TEXT("text/vcard"));
    }
     
    StringBuffer vcard;
    [self personToVcard:person vcard:vcard];
    item->setData((void*)vcard.c_str(), vcard.length());
}



- (void) convertFromSyncItem:
                (SyncItem*) item
    native:     (CFTypeRef) person
{     
    StringBuffer temp((char*)item->getData());
    [self vCardToPerson:temp person:person];
}


/**
 * Person Helper Functions
**/

- (void) setPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    value:      (CFStringRef)   value
{
    [self setPersonProperty:person
        property:property
        value:value
        part:nil
        ];
}
    
- (void) setPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    value:      (CFStringRef)   value
    part:       (CFStringRef)   part // Only used for Multi Prop Values
{
    [self setPersonProperty:person
        property:property
        value:value
        part:part
        label:nil
        ];
}
    
- (void) setPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    value:      (CFStringRef)   value
    label:      (CFStringRef)   label // Only used for Multi Prop Values
{
    [self setPersonProperty:person
        property:property
        value:value
        part:nil
        label:label
        ];
}    

- (void) setPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    value:      (CFStringRef)   value
    part:       (CFStringRef)   part  // Only used for Multi Prop Values - @"" means ignore
    label:      (CFStringRef)   label // Only used for Multi Prop Values
{
    /* Assume string type!
    kABPersonFirstNameProperty:          // First name - kABStringPropertyType
    kABPersonLastNameProperty:           // Last name - kABStringPropertyType
    kABPersonMiddleNameProperty:         // Middle name - kABStringPropertyType
    kABPersonPrefixProperty:             // Prefix ("Sir" "Duke" "General") - kABStringPropertyType
    kABPersonSuffixProperty:             // Suffix ("Jr." "Sr." "III") - kABStringPropertyType
    kABPersonNicknameProperty:           // Nickname - kABStringPropertyType
    kABPersonFirstNamePhoneticProperty:  // First name Phonetic - kABStringPropertyType
    kABPersonLastNamePhoneticProperty:   // Last name Phonetic - kABStringPropertyType
    kABPersonMiddleNamePhoneticProperty: // Middle name Phonetic - kABStringPropertyType
    kABPersonOrganizationProperty:       // Company name - kABStringPropertyType
    kABPersonJobTitleProperty:           // Job Title - kABStringPropertyType
    kABPersonDepartmentProperty:         // Department name - kABStringPropertyType
    kABPersonNoteProperty:               // Note - kABStringPropertyType
    */
    
    
    if (!value || !person)
    {
        return;
    }
    
    CFTypeRef realValue = nil;
    
    // kABMultiStringPropertyType
    if (property == kABPersonEmailProperty ||               // Email(s) - kABMultiStringPropertyType
        property == kABPersonURLProperty   ||               // URL - kABMultiStringPropertyType
        property == kABPersonRelatedNamesProperty ||        // Names - kABMultiStringPropertyType
        property == kABPersonPhoneProperty)                 // Generic phone number - kABMultiStringPropertyType
    { 
        realValue = ABRecordCopyValue(person, property);
        if (!realValue)
        {
            realValue = ABMultiValueCreateMutable(kABStringPropertyType);
        }
        else
        {
            CFTypeRef tempValue = ABMultiValueCreateMutableCopy(realValue);
            CFRelease(realValue);
            realValue = tempValue;
        }
        
        // TODO CHECK THIS COMPARISON!!!
        // The apple documentation says ABMultiValueGetPropertyType returns the type
        // of the values within it, and it really contains Strings, not MultiStrings
        if (ABMultiValueGetPropertyType(realValue) != kABStringPropertyType
            &&
            ABMultiValueGetPropertyType(realValue) != kABMultiStringPropertyType)
        {
            CFRelease(realValue);
            CFRelease(value);
            return;
        }
        
        CFStringRef aLabel = nil;
    
        int index;
        int total = ABMultiValueGetCount(realValue);
        for (index = 0; index < total; index++)
        {
            aLabel = ABMultiValueCopyLabelAtIndex(realValue, index);
            if (aLabel)
            {
                if (CFStringCompare(aLabel, label, 0) == 0)
                {
                    CFRelease(aLabel);
                    aLabel = nil;
                    break;
                }
                CFRelease(aLabel);
                aLabel = nil;
            }
        }
        
        
        if (index < total)
        {
            if (CFStringGetLength(value) == 0)
            {
                ABMultiValueRemoveValueAndLabelAtIndex(realValue, index);
                CFRelease(value);
            }
            else
            {
                ABMultiValueReplaceValueAtIndex(realValue, value, index);
            }
        }
        else
        {
            if (CFStringGetLength(value) != 0)
            {
                ABMultiValueAddValueAndLabel(realValue, value, label, nil);
            }
            else
            {
                CFRelease(value);
            }
        }
    }
    else
    if (property == kABPersonAddressProperty ||             // Street Address - kABMultiDictionaryPropertyType
        property == kABPersonInstantMessageProperty)        // IM Addresses - kABMultiDictionaryPropertyType
    {
        realValue = ABRecordCopyValue(person, property);
        if (!realValue)
        {
            realValue = ABMultiValueCreateMutable(kABDictionaryPropertyType);
        }
        else
        {
            CFTypeRef tempValue = ABMultiValueCreateMutableCopy(realValue);
            CFRelease(realValue);
            realValue = tempValue;
        }
        
        // TODO CHECK THIS COMPARISON!!!
        // The apple documentation says ABMultiValueGetPropertyType returns the type
        // of the values within it, and it really contains Dictionary, not MultiDictionary
        if (ABMultiValueGetPropertyType(realValue) != kABDictionaryPropertyType
            &&
            ABMultiValueGetPropertyType(realValue) != kABMultiDictionaryPropertyType)
        {
            CFRelease(value);
            CFRelease(realValue);
            return;
        }
        
        CFMutableDictionaryRef indexValue = nil;
        CFStringRef aLabel = nil;
        
        int index;
        int total = ABMultiValueGetCount(realValue);
        for (index = 0; index < total; index++)
        {
            aLabel = ABMultiValueCopyLabelAtIndex(realValue, index);
            if (aLabel)
            {
                if (CFStringCompare(aLabel, label, 0) == 0)
                {
                    CFDictionaryRef tempdict = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(realValue,index);
                    if (!tempdict) {
                        continue;
                    }
                    indexValue = (CFMutableDictionaryRef)CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, 
                            tempdict
                        );
                    CFRelease(tempdict);
                    CFRelease(aLabel);
                    aLabel = nil;
                    break;
                }
                CFRelease(aLabel);
                aLabel = nil;
            }
        }
        
        if (!indexValue)
        {
            indexValue = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nil, nil);
        }

        CFDictionarySetValue(indexValue, part, value);

        if (index < total)
        {
            if (CFStringGetLength(value) == 0 && CFDictionaryGetCount(indexValue) == 0)
            {
                ABMultiValueRemoveValueAndLabelAtIndex(realValue, index);
                CFRelease(indexValue);
            }
            else
            {
                ABMultiValueReplaceValueAtIndex(realValue, indexValue, index);
            }
        }
        else
        {
            if (CFStringGetLength(value) != 0)
            {
                ABMultiValueAddValueAndLabel(realValue, indexValue, label, nil);
            }
            else
            {
                CFRelease(indexValue);
            }
        }
        if(indexValue){
            CFRelease(indexValue);
        }
    }
    else
    if (property == kABPersonBirthdayProperty)           // Birthday associated with this person - kABDateTimePropertyType
    {
        if (CFStringGetLength(value) == 0)
        {
            CFRelease(value);
            realValue = nil;
        }  
        else
        {
            int year, month, day;
            Funambol::StringBuffer sb = CFString2Std((CFStringRef)value);
            const char * tempvalue = sb.c_str();
            CFRelease(value);
            if (!tempvalue || !tempvalue[0]) {
                return;
            }
            if (sscanf(tempvalue, TEXT("%04d-%02d-%02d"), &year, &month, &day) == 3 || 
                sscanf(tempvalue, TEXT("%04d%02d%02d"), &year, &month, &day) == 3)
            {
                CFGregorianDate date;
                date.year   = year;
                date.month  = month;
                date.day    = day;
                date.hour   = 0;
                date.minute = 0;
                date.second = 0;

                /*
                 * The iPhone stores absolute times for dates, but
                 * interprets them according to the current time zone.
                 * The effect is that a birthday changes as the system
                 * timezone is changed.
                 *
                 * To mitigate this problem dates are created with
                 * an absolute time in the current time zone, just like
                 * the iPhone GUI does.
                 */
                CFTimeZoneRef tz = CFTimeZoneCopyDefault();
                CFDateRef cfdate(CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(date, tz)));
                CFRelease(tz);
                realValue = cfdate;
            }    
        }
    }
    else
    if (property == kABPersonDateProperty)               // Dates associated with this person - kABMultiDatePropertyType
    {
        // TODO Incomplete
        CFRelease(value);
        return;
    }
    else
    if (property == kABPersonKindProperty)               // Person/Organization - kABIntegerPropertyType
    {
        CFRelease(value);
        return;
    }
    else
    {
        if (CFStringGetLength(value) > 0)
        {
            realValue = value;
        }
        else
        {
            CFRelease(value);
        }
    }
    

    if (realValue == nil)
    {
        if (!ABRecordRemoveValue(person, property, error))
        {
            [self throwError:@"Error removing property"];
        }
    }
    else
    {
        if (!ABRecordSetValue(person, property, realValue, error)) {
            if (verbose) {
                NSMutableString * desc = [[NSMutableString alloc] init];
                [desc appendString:@"Error setting property "];
                //[desc appendString: property ];
                [self throwError: desc];
            } else {
                [self throwError:@"Error setting property"];
            }
        }
    }
    if(realValue){
        CFRelease(realValue);
    }
}




- (CFStringRef) getPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
{
    return [self getPersonProperty:person
        property:property
        part:nil
        ];
}
    
- (CFStringRef) getPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    part:       (CFStringRef)   part // Only used for Multi Prop Values
{
    return [self getPersonProperty:person
        property:property
        part:part
        label:nil
        ];
}
    
- (CFStringRef) getPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    label:      (CFStringRef)   label // Only used for Multi Prop Values
{
    return [self getPersonProperty:person
        property:property
        part:nil
        label:label
        ];
}    

- (CFStringRef) getPersonProperty:
                (ABRecordRef)   person
    property:   (ABPropertyID)  property
    part:       (CFStringRef)   part  // Only used for Multi Prop Values - @"" means ignore
    label:      (CFStringRef)   label // Only used for Multi Prop Values
{
    /* Assume string type!
    kABPersonFirstNameProperty:          // First name - kABStringPropertyType
    kABPersonLastNameProperty:           // Last name - kABStringPropertyType
    kABPersonMiddleNameProperty:         // Middle name - kABStringPropertyType
    kABPersonPrefixProperty:             // Prefix ("Sir" "Duke" "General") - kABStringPropertyType
    kABPersonSuffixProperty:             // Suffix ("Jr." "Sr." "III") - kABStringPropertyType
    kABPersonNicknameProperty:           // Nickname - kABStringPropertyType
    kABPersonFirstNamePhoneticProperty:  // First name Phonetic - kABStringPropertyType
    kABPersonLastNamePhoneticProperty:   // Last name Phonetic - kABStringPropertyType
    kABPersonMiddleNamePhoneticProperty: // Middle name Phonetic - kABStringPropertyType
    kABPersonOrganizationProperty:       // Company name - kABStringPropertyType
    kABPersonJobTitleProperty:           // Job Title - kABStringPropertyType
    kABPersonDepartmentProperty:         // Department name - kABStringPropertyType
    kABPersonNoteProperty:               // Note - kABStringPropertyType
    */
    
    CFTypeRef value;
    
    if (property == kABPersonModificationDateProperty)
    {
        CFDateRef cfdate = (CFDateRef)ABRecordCopyValue(person, property);
        if (!cfdate)
            return nil;
        CFTimeZoneRef tz(CFTimeZoneCopyDefault());
        CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(
            CFDateGetAbsoluteTime(
                cfdate
                ),
            tz);

        CFStringRef outStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
            CFSTR("%04d%02d%02d%02d%02d%02.3f"),
            date.year,
            date.month,
            date.day,
            date.hour,
            date.minute,
            date.second);

        CFRelease(cfdate);
        CFRelease(tz);
        
        return outStr;
    /*
        int val;
        CFNumberRef num = (CFNumberRef)ABRecordCopyValue(person, property);
        CFTypeRef type = (CFNumberRef)ABRecordCopyValue(person, property);
        CFStringRef desc = CFCopyTypeIDDescription(CFGetTypeID(type));
        if (!num || !CFNumberGetValue(num, kCFNumberIntType, &type))
        {
            //LOG.debug("getPersonProperty: some truncation occurred getting timestamp");
        }
        CFStringRef outStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), val);
        CFRelease(num);
        return outStr;
        */
    }
    else if (property == kABPersonEmailProperty ||          // Email(s) - kABMultiStringPropertyType
        property == kABPersonURLProperty   ||               // URL - kABMultiStringPropertyType
        property == kABPersonRelatedNamesProperty ||        // Names - kABMultiStringPropertyType
        property == kABPersonPhoneProperty)                 // Generic phone number - kABMultiStringPropertyType
    {
        value = ABRecordCopyValue(person, property);
        if (!value) {
            return CFSTR("");
        }
         
        CFStringRef aLabel;
        CFStringRef ret = CFSTR("");
                  
        for (int x = 0; x < ABMultiValueGetCount(value); x++)
        {
            aLabel = ABMultiValueCopyLabelAtIndex(value, x);
            if (aLabel)
            {
                if (CFStringCompare(aLabel, label, 0) == kCFCompareEqualTo)
                {
                    ret = (CFStringRef)ABMultiValueCopyValueAtIndex(value, x);
                    CFRelease(aLabel);
                    break;
                }
                CFRelease(aLabel);
                aLabel = nil;
            }
        }
        
        CFRelease(value);
        
        return ret;
    }
    else
    if (property == kABPersonAddressProperty ||             // Street address - kABMultiDictionaryPropertyType
        property == kABPersonInstantMessageProperty)        // Instant Messaging - kABMultiDictionaryPropertyType
    {
        value = ABRecordCopyValue(person, property);
        if (!value) {
            return nil;
        }
        
        CFDictionaryRef indexValue = nil;
        
        int index;
        int total = ABMultiValueGetCount(value);
        CFStringRef aLabel;
        for (index = 0; index < total; index++)
        {
            aLabel = ABMultiValueCopyLabelAtIndex(value, index);
            if (aLabel)
            {
                if (CFStringCompare(aLabel, label, 0) == 0)
                {
                    indexValue = (CFDictionaryRef)ABMultiValueCopyValueAtIndex(value,index);
                    CFRelease(aLabel);
                    aLabel = nil;
                    break;
                }
                CFRelease(aLabel);
                aLabel = nil;
            }
        }
        
        CFRelease(value);
        
        if (!indexValue)
        {
            return CFSTR("");
        }

        CFStringRef ret;
        if (!CFDictionaryGetValueIfPresent(indexValue, part, (const void**)(&ret))) {
            CFRelease(indexValue);
            return CFSTR("");
        }
        CFRelease(indexValue);

        ret = CFStringCreateCopy(kCFAllocatorDefault, ret);
        
        return ret;
    }
    else
    if (property == kABPersonBirthdayProperty)           // Birthday associated with this person - kABDateTimePropertyType
    {
        CFDateRef cfdate = (CFDateRef)ABRecordCopyValue(person, property);
        if (!cfdate)
            return CFSTR("");
        CFTimeZoneRef tz(CFTimeZoneCopyDefault());
        CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(
            CFDateGetAbsoluteTime(
                cfdate
                ),
            tz);

        CFStringRef outStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
                                                      CFSTR("%04d-%02d-%02d"), date.year, date.month, date.day);

        CFRelease(cfdate);
        CFRelease(tz);
        
        return outStr;
    }
    else
    if (property == kABPersonDateProperty)               // Dates associated with this person - kABMultiDatePropertyType
    {
        // TODO Incomplete
        return nil;
    }
    else
    if (property == kABPersonKindProperty)               // Person/Organization - kABIntegerPropertyType
    {
        //We dont use this
        return nil;
    }
    else
    {
        CFStringRef val = (CFStringRef)ABRecordCopyValue(person, property);
        if (!val)
            val = CFSTR("");
        return val;
    }
    
    // Some other unsupported property
    return nil;
}



- (void) setPersonPhoto:
                (ABRecordRef)           person
    value:      (CFDataRef)             data
{
    CFErrorRef err;

    if (!ABPersonSetImageData(person, data, &err))
    {
        // TODO : Handle error
        CFRelease(err);
    }
}
    
- (CFDataRef) getPersonPhoto:
                (ABRecordRef)           person
{
    CFDataRef ret = nil;
    CFDataRef data = ABPersonCopyImageData(person);
    if (data)
    {
        //CFMutableDataRef mdata = CFDataCreateMutableCopy(kCFAllocatorDefault, CFDataGetLength(data), data);
        UIImage * img = [[UIImage alloc] initWithData: (const NSData*)data];
        if (img)
        {
            ret = (CFDataRef)UIImageJPEGRepresentation(img, 10.0);
            /*
            StringBuffer filename("/test.jpg");
            FILE * file = fopen(filename.c_str(), "w");
            fwrite(CFDataGetBytePtr(ret), CFDataGetLength(ret), 1, file);
            fclose(file);
            */
            [img release];
        }
        CFRelease(data);
    }
    return ret;
}

- (void) clearPersonPhoto:
                (ABRecordRef)           person
{
    CFErrorRef err;
    if (!ABPersonRemoveImageData(person, &err))
    {
        //TODO : Handle Error
        CFRelease(err);
    }
}



/** parses vcard and stores result in person */
- (void) vCardToPerson:
                (StringBuffer&)   vcardString
    person:     (ABRecordRef)   person
{

    VObject * vobj = VConverter::parse(vcardString.c_str());
    if (vobj == NULL) {
        // TODO: remove throw!
        [self throwError: @"Unable to parse vCard string"];
        return;
    }
    
    int  homeTel     = 0;
    int  businessTel = 0;
    bool otherAdr    = false;
    bool otherTel    = false;
    bool otherEmail  = false;
    
    // walk through all properties and handle them
    int propindex = 0;
    VProperty * vprop;
    while ((vprop = vobj->getProperty(propindex)) != NULL) {
    
        propindex++;
    
        char * n = vprop->getName();
        char * v = vprop->getValue();
        
        if (!n)
            continue;
        if(!v)
            continue;
        
        CFStringRef name  = Std2CFString(n);
        CFStringRef value = Std2CFString(v);
        
        if (!name)
            continue;
        if (!value)
            continue;
        
        if(CFStringCompare(name, CFSTR("N"), 0) == 0) {
            CFRelease(value);
            if(value = Std2CFString(vprop->getPropComponent(1))) {
                [self setPersonProperty:person  property:kABPersonLastNameProperty      value:value];
            }
            if(value = Std2CFString(vprop->getPropComponent(2))) {
                [self setPersonProperty:person  property:kABPersonFirstNameProperty     value:value];
            }
            if(value = Std2CFString(vprop->getPropComponent(3))) {
                [self setPersonProperty:person  property:kABPersonMiddleNameProperty    value:value];
            }
            if(value = Std2CFString(vprop->getPropComponent(4))) {
                [self setPersonProperty:person  property:kABPersonPrefixProperty        value:value];
            }
            if(value = Std2CFString(vprop->getPropComponent(5))) {
                [self setPersonProperty:person  property:kABPersonSuffixProperty        value:value];
            }
        }
        else if(CFStringCompare(name, CFSTR("BDAY"), 0) == 0) {
            [self setPersonProperty:person  property:kABPersonBirthdayProperty      value:value];
        }
        else if(CFStringCompare(name, CFSTR("NOTE"), 0) == 0) {
            [self setPersonProperty:person  property:kABPersonNoteProperty          value:value];
        }
        else if(CFStringCompare(name, CFSTR("CLASS"), 0) == 0) {
            CFRelease(value); // Ignore
        }
        else if(CFStringCompare(name, CFSTR("CATEGORIES"), 0) == 0) {
            CFRelease(value); // Ignore
        }
        else if(CFStringCompare(name, CFSTR("EMAIL"), 0) == 0) {
            // Mapping is:
            // Email1Address <-> EMAIL;INTERNET:
            // Email2Address <-> EMAIL;INTERNET;HOME:
            // Email3Address <-> EMAIL;INTERNET;WORK:
            if (vprop->containsParameter((char*)TEXT("HOME"))) {
                if (vprop->containsParameter((char*)TEXT("X-FUNAMBOL-INSTANTMESSENGER"))) {
                    // TODO IMs require more complex mapping
                    /*
                    */
                    CFRelease(value);
                }
                else {
                    [self setPersonProperty:person  property:kABPersonEmailProperty         value:value     label:kABHomeLabel];
                }
            }
            else if (vprop->containsParameter((char*)TEXT("WORK"))) {
                [self setPersonProperty:person  property:kABPersonEmailProperty         value:value     label:kABWorkLabel];
            }
            else if (!otherEmail) {
                otherEmail = true;
                [self setPersonProperty:person  property:kABPersonEmailProperty         value:value     label:kABOtherLabel];
            }
            else {
            }
        }
        else if(CFStringCompare(name, CFSTR("FN"), 0) == 0) {
            CFRelease(value); // Ignore
        }
        else if(CFStringCompare(name, CFSTR("NICKNAME"), 0) == 0) {
            [self setPersonProperty:person  property:kABPersonNicknameProperty      value:value];
        }
        else if(CFStringCompare(name, CFSTR("PRIORITY"), 0) == 0) {
            CFRelease(value); // Ignore
        }
        else if(CFStringCompare(name, CFSTR("ORG"), 0) == 0) {
            CFRelease(value);
        
            if(value = Std2CFString(vprop->getPropComponent(1)))
                [self setPersonProperty:person  property:kABPersonOrganizationProperty  value:value];
            if(value = Std2CFString(vprop->getPropComponent(2)))
                [self setPersonProperty:person  property:kABPersonDepartmentProperty    value:value];
            
            //TODO Can we save office location?
            /*
            if(value = (NSString *)Std2CFString(vprop->getPropComponent(3)))
                [self setPersonProperty:person  property:    value:value];
            */
        }
        else if(CFStringCompare(name, CFSTR("ROLE"), 0) == 0) {
            CFRelease(value); // Ignore
        }

        //
        // ---- Telephone fields ----
        //
        else if(CFStringCompare(name, CFSTR("TEL"), 0) == 0) {
            if(vprop->containsParameter((char*)TEXT("WORK"))) {
                if (vprop->containsParameter((char*)TEXT("FAX"))) {
                    [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneWorkFAXLabel];
                }
                else if (vprop->containsParameter((char*)TEXT("PREF"))) {
                    [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneCompanyLabel];
                }
                else {      // default, would be "else if (vprop->containsParameter("VOICE"))"
                    if(businessTel == 0) {
                        [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABWorkLabel];
                        businessTel++;
                    }
                    else{
                        // TODO Can we save any more business numbers?
                        /* 
                        [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value];
                        //setProperty("Business2TelephoneNumber", element);
                        */
                        CFRelease(value);
                    }
                }
            }
            else if (vprop->containsParameter((char*)TEXT("CAR"))) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneCarLabel];
            }
            else if(vprop->containsParameter((char*)TEXT("HOME"))) {
                if(vprop->containsParameter((char*)TEXT("FAX"))) {
                    [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneHomeFAXLabel];
                }
                else {      // default, would be "else if (vprop->containsParameter("VOICE"))"
                    if(homeTel == 0) {
                        [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABHomeLabel];
                        homeTel++;
                    }
                    else {
                        // TODO Can we save more home numbers?
                        /*
                        [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value];
                        //setProperty("Home2TelephoneNumber", element);
                        */
                        CFRelease(value);
                    }
                }
            }
            else if (vprop->containsParameter((char*)TEXT("CELL"))) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneMobileLabel];
            }
            else if (vprop->containsParameter((char*)TEXT("FAX"))) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneFAXLabel];
            }
            else if(vprop->containsParameter((char*)TEXT("PAGER"))) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhonePagerLabel];
            }
            else if (vprop->containsParameter((char*)TEXT("PREF"))) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABPersonPhoneMainLabel];
            }
            /* Not supported
            else if(vprop->containsParameter("X-FUNAMBOL-CALLBACK")) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value];
                //setProperty("CallbackTelephoneNumber", element);
            }
            else if(vprop->containsParameter("X-FUNAMBOL-RADIO")) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value];
                //setProperty("RadioTelephoneNumber", element);
            }
            else if(vprop->containsParameter("X-FUNAMBOL-TELEX")) {
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value];
                //setProperty("TelexNumber", element);
            }
            */
            else if (!otherTel) {      // default, would be "else if (vprop->containsParameter("VOICE"))"
                otherTel = true;
                [self setPersonProperty:person  property:kABPersonPhoneProperty         value:value     label:kABOtherLabel];
            }
            else {
                CFRelease(value);
            }
        }
        
        else if(CFStringCompare(name, CFSTR("TITLE"), 0) == 0) {
            [self setPersonProperty:person  property:kABPersonJobTitleProperty      value:value];
        }

        else if(CFStringCompare(name, CFSTR("PHOTO"), 0) == 0) {
            CFRelease(value);
            if (strlen(v) != 0)
            {
                char * content = new char[strlen(v)];
                int len = b64_decode(content, v);
                if (getLastErrorCode() != 0)
                {
                    // Handle error
                }
                else
                {
                    CFDataRef val = CFDataCreate(kCFAllocatorDefault, (const UInt8*)content, len);
                    [self setPersonPhoto:person value:val];
                }
                delete [] content;
            }
            else
            {
                [self clearPersonPhoto:person];
            }
        }

        //
        // ---- URL fields ----
        //
        else if(CFStringCompare(name, CFSTR("URL"), 0) == 0) {
            if (vprop->containsParameter((char*)TEXT("WORK"))) {
                [self setPersonProperty:person  property:kABPersonURLProperty           value:value     label:kABWorkLabel];
            }
            else if(vprop->containsParameter((char*)TEXT("HOME"))) {
                [self setPersonProperty:person  property:kABPersonURLProperty           value:value     label:kABHomeLabel];
            }
            else {
                [self setPersonProperty:person  property:kABPersonURLProperty           value:value     label:kABOtherLabel];
            }
        }

        //
        // ---- ADR fields ----
        //
        else if(CFStringCompare(name, CFSTR("ADR"), 0) == 0) {
            if(vprop->containsParameter((char*)TEXT("HOME"))) {
                if(value = Std2CFString(vprop->getPropComponent(1))) {
                    ;//[self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABHomeLabel];
                    //setProperty("HomeAddressPostOfficeBox", element);
                    CFRelease(value);
                }
                if(value = Std2CFString(vprop->getPropComponent(2))) {
                    ;//[self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABHomeLabel];
                    //  setProperty("HomeAddressExtended",      element);
                    CFRelease(value);
                }
                if(value = Std2CFString(vprop->getPropComponent(3))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABHomeLabel];
                    //  setProperty("HomeAddressStreet",        element);
                }
                if(value = Std2CFString(vprop->getPropComponent(4))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressCityKey        label:kABHomeLabel];
                    //  setProperty("HomeAddressCity",          element);
                }
                if(value = Std2CFString(vprop->getPropComponent(5))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStateKey       label:kABHomeLabel];
                    //  setProperty("HomeAddressState",         element);
                }
                if(value = Std2CFString(vprop->getPropComponent(6))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressZIPKey         label:kABHomeLabel];
                    //  setProperty("HomeAddressPostalCode",    element);
                }
                if(value = Std2CFString(vprop->getPropComponent(7))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressCountryKey     label:kABHomeLabel];
                    //  setProperty("HomeAddressCountry",       element);
                }
            }
            else if(vprop->containsParameter((char*)TEXT("WORK"))) {
                if(value = Std2CFString(vprop->getPropComponent(1))) {
                    //[self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABWorkLabel];
                    //  setProperty("BusinessAddressPostOfficeBox", element);
                    CFRelease(value);
                }
                if(value = Std2CFString(vprop->getPropComponent(2))) {
                    //[self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABWorkLabel];
                    //  setProperty("BusinessAddressExtended",      element);
                    CFRelease(value);
                }
                if(value = Std2CFString(vprop->getPropComponent(3))) { 
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABWorkLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(4))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressCityKey        label:kABWorkLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(5))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStateKey       label:kABWorkLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(6))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressZIPKey         label:kABWorkLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(7))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressCountryKey     label:kABWorkLabel];
                }
            }
            else if (!otherAdr) {
                otherAdr = true;
                if(value = Std2CFString(vprop->getPropComponent(1))) {
                    //[self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABOtherLabel];
                    //  setProperty("OtherAddressPostOfficeBox", element);
                    CFRelease(value);
                }
                if(value = Std2CFString(vprop->getPropComponent(2))) {
                    //[self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABOtherLabel];
                    //  setProperty("OtherAddressExtended",      element);
                    CFRelease(value);
                }
                if(value = Std2CFString(vprop->getPropComponent(3))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStreetKey      label:kABOtherLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(4))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressCityKey        label:kABOtherLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(5))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressStateKey       label:kABOtherLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(6))) {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressZIPKey         label:kABOtherLabel];
                }
                if(value = Std2CFString(vprop->getPropComponent(7)))
                {
                    [self setPersonProperty:person  property:kABPersonAddressProperty       value:value     part:kABPersonAddressCountryKey     label:kABOtherLabel];
                }
            }

        }

        //
        // ---- Funambol defined properties ----
        //
        // TODO 
        /*
        else if([name isEqual: @"X-FUNAMBOL-ANNIVERSARY"]) {
            setProperty("Anniversary", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-CHILDREN"]) {
            setProperty("Children", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-COMPANIES"]) {
            setProperty("Companies", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-FOLDER"]) {
            setProperty("Folder", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-GENDER"]) {
            setProperty("Gender", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-HOBBIES"]) {
            setProperty("Hobby", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-INITIALS"]) {
            setProperty("Initials", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-LANGUAGES"]) {
            setProperty("Language", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-MANAGER"]) {
            setProperty("ManagerName", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-MILEAGE"]) {
            setProperty("Mileage", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-SPOUSE"]) {
            setProperty("Spouse", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-SUBJECT"]) {
            setProperty("Subject", element);
        }
        else if([name isEqual: @"X-FUNAMBOL-TELEX"]) {
            setProperty("TelexNumber", element);
        }
        */
        else
        {
            CFRelease(value);
        }
        
        CFRelease(name);
    }
    
    delete vobj;
}



/** creates a vcard from a person */
- (void) personToVcard:
                (ABRecordRef)   person
    vcard:      (StringBuffer&)   vcardString
{

    VObject* vo = new VObject();
    CFStringRef value;
    VProperty* vp = nil;
    bool found = false;

    vp = new VProperty(TEXT("BEGIN"), TEXT("VCARD"));
    vo->addProperty(vp);
    delete vp; vp = NULL;

    if (version3) {
        vp = new VProperty(TEXT("VERSION"), TEXT("3.0"));
    }
    else {
        vp = new VProperty(TEXT("VERSION"), TEXT("2.1"));
    }
    vo->addProperty(vp);
    delete vp; vp = NULL;

    // ------- Name -------
    // Add only if at least 1 property is supported, but include
    // all elements in the right order.
    found = false;
    vp = new VProperty(TEXT("N"));
    if (value = [self getPersonProperty:person property:kABPersonLastNameProperty]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }else{
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonFirstNameProperty]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }else{
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonMiddleNameProperty]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }else{
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonPrefixProperty]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }else{
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonSuffixProperty]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }else{
        vp->addValue("");
    }
    vo->addProperty(vp);
    delete vp; vp = NULL;


    if (value = [self getPersonProperty:person property:kABPersonBirthdayProperty]) {
        vp = new VProperty(TEXT("BDAY"), CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonNoteProperty]) {
        vp = new VProperty(TEXT("NOTE"), CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    
    // Telephone numbers

    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABWorkLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("VOICE"), NULL);
        vp->addParameter(TEXT("WORK"),  NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABPersonPhoneWorkFAXLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("WORK"), NULL);
        vp->addParameter(TEXT("FAX"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABPersonPhoneCompanyLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("WORK"), NULL);
        vp->addParameter(TEXT("PREF"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABHomeLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("VOICE"), NULL);
        vp->addParameter(TEXT("HOME"),  NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABPersonPhoneHomeFAXLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("HOME"), NULL);
        vp->addParameter(TEXT("FAX"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABPersonPhoneFAXLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("FAX"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty label:kABPersonPhoneCarLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("CAR"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty  label:kABPersonPhoneMobileLabel] ) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("CELL"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty    label:kABOtherLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("VOICE"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty    label:kABPersonPhonePagerLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("PAGER"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonPhoneProperty    label:kABPersonPhoneMainLabel]) {
        vp = new VProperty(TEXT("TEL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("PREF"),  NULL);
        vp->addParameter(TEXT("VOICE"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    // End Tel

    
    if (value = [self getPersonProperty:person property:kABPersonURLProperty label:kABWorkLabel]) {
        vp = new VProperty(TEXT("URL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("WORK"),  NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonURLProperty    label:kABHomeLabel]) {
        vp = new VProperty(TEXT("URL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("HOME"), NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonURLProperty    label:kABPersonHomePageLabel]) {
        vp = new VProperty(TEXT("URL"), CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }

    /*
    // TODO Can we support categories?
    if (getProperty("Categories", element)) {
        vp = new VProperty("CATEGORIES", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO Can we save FileAs?
    if (getProperty("FileAs", element)) {
        vp = new VProperty("FN", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */

    // Mapping is:
    // Email1Address <-> EMAIL;INTERNET:
    // Email2Address <-> EMAIL;INTERNET;HOME:
    // Email3Address <-> EMAIL;INTERNET;WORK:
    
    
    if (value = [self getPersonProperty:person property:kABPersonEmailProperty label:kABOtherLabel]) {
        VProperty vp(TEXT("EMAIL"), CFString2Std(value).c_str()); // XXX
        vp.addParameter(TEXT("INTERNET"), NULL);
        vo->addProperty(&vp);
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonEmailProperty label:kABHomeLabel]) {
        vp = new VProperty(TEXT("EMAIL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("INTERNET"), NULL);
        vp->addParameter(TEXT("HOME"),     NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }
    if (value = [self getPersonProperty:person property:kABPersonEmailProperty label:kABWorkLabel]) {
        vp = new VProperty(TEXT("EMAIL"), CFString2Std(value).c_str());
        vp->addParameter(TEXT("INTERNET"), NULL);
        vp->addParameter(TEXT("WORK"),     NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }

    if (value = [self getPersonProperty:person property:kABPersonJobTitleProperty] ) {
        vp = new VProperty(TEXT("TITLE"), CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }

    /*
    // TODO Importance supported?
    if (getProperty("Importance", element)) {
        vp = new VProperty("PRIORITY", CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */

    if (value = [self getPersonProperty:person property:kABPersonNicknameProperty]) {
        vp = new VProperty(TEXT("NICKNAME"), CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }

    /*
    // TODO Profession supported?
    if (getProperty("Profession", element)) {
        vp = new VProperty("ROLE", CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO Sensitivity supported?
    if (getProperty("Sensitivity", element)) {
        long sensitivity = _wtoi(CFString2Std(value).c_str());
        vp = new VProperty(TEXT("CLASS"));
        if(sensitivity == winPrivate) {
            vp->addValue(TEXT("PRIVATE"));
        }
        else if (sensitivity == winConfidential) {
            vp->addValue(TEXT("CONFIDENTIAL"));
        }
        else {  // default value
            vp->addValue(TEXT("PUBLIC"));
        }
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */


    // ----- ORG -----
    // Add only if at least 1 property is supported, but include
    // all elements in the right order.
    found = false;
    vp = new VProperty(TEXT("ORG"));
    if (value = [self getPersonProperty:person property:kABPersonOrganizationProperty] ) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }
    else
    {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonDepartmentProperty]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    }
    else
    {
        vp->addValue("");
    }
    // TODO Office Location?
    //if (value = [self getPersonProperty:person property:kABPersonDepartmentProperty])   found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    vo->addProperty(vp);
    delete vp; vp = NULL;


    // ----- Address HOME -----
    // Add only if at least 1 property is supported, but include
    // all elements in the right order.
    // "AddressPostOfficeBox" is not supported by WM.
    // "AddressExtended" is not supported by Outlook/WM.
    found = false;
    vp = new VProperty(TEXT("ADR"));
    vp->addParameter(TEXT("HOME"), NULL);
    // TODO no PO Box Support
    //if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey  label:kABHomeLabel])   found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    // TODO no Extended Street Address Support
    //if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey  label:kABHomeLabel])  found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey      label:kABHomeLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressCityKey        label:kABHomeLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStateKey       label:kABHomeLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressZIPKey         label:kABHomeLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressCountryKey     label:kABHomeLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }

    vo->addProperty(vp);
    delete vp; vp = NULL;


    // ----- Address -----
    // Add only if at least 1 property is supported, but include
    // all elements in the right order.
    // "AddressPostOfficeBox" is not supported by WM.
    // "AddressExtended" is not supported by Outlook/WM.
    found = false;
    vp = new VProperty(TEXT("ADR"));
    // TODO no PO Box Support
    //if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey  label:kABOtherLabel])  found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    // TODO no Extended Street Address Support
    //if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey  label:kABOtherLabel])  found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey      label:kABOtherLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressCityKey        label:kABOtherLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStateKey       label:kABOtherLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressZIPKey         label:kABOtherLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressCountryKey     label:kABOtherLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }

    vo->addProperty(vp);
    delete vp; vp = NULL;


    // ----- Address WORK -----
    // Add only if at least 1 property is supported, but include
    // all elements in the right order.
    // "AddressPostOfficeBox" is not supported by WM.
    // "AddressExtended" is not supported by Outlook/WM.
    found = false;
    vp = new VProperty(TEXT("ADR"));
    vp->addParameter(TEXT("WORK"), NULL);
    // TODO no PO Box Support
    //if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey  label:kABWorkLabel])  found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    // TODO no Extended Street Address Support
    //if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey  label:kABWorkLabel])  found = true;
    vp->addValue(TEXT(""));//CFString2Std(value).c_str());
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStreetKey      label:kABWorkLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressCityKey        label:kABWorkLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressStateKey       label:kABWorkLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressZIPKey         label:kABWorkLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }
    if (value = [self getPersonProperty:person property:kABPersonAddressProperty    part:kABPersonAddressCountryKey     label:kABWorkLabel]) {
        vp->addValue(CFString2Std(value).c_str());
        CFRelease(value);
    } else {
        vp->addValue("");
    }

    vo->addProperty(vp);
    delete vp; vp = NULL;

    CFDataRef val = nil;
    //PHOTO
    // Cannot reliably sync photos until Apple gives us info about the type stored!!
    if (val = [self getPersonPhoto:person]) {
        int len = CFDataGetLength(val);
        char * content = uuencode((const char*)CFDataGetBytePtr(val), len);
        if (content) {
            
            StringBuffer contentBuf(content);
            indent(contentBuf);
            
            vp = new VProperty("PHOTO", contentBuf.c_str());
            vp->addParameter("TYPE", "JPEG");
            vp->addParameter("ENCODING", "b");
            vp->addParameter("CONTENT-VALUE", "UNCHANGED");
            vo->addProperty(vp);
            delete vp; vp = NULL;
        } else {
            vp = new VProperty("PHOTO", "");
            vo->addProperty(vp);
            delete vp; vp = NULL;
        }
        delete [] content;
        CFRelease(val);
    }else{
        vp = new VProperty("PHOTO", "");
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }

    //
    // ---- Funambol defined properties ----
    // Support for other fields that don't have a
    // specific correspondence in vCard.

    if (value = [self getPersonProperty:person property:kABPersonDateProperty   label:kABPersonAnniversaryLabel]) {
        vp = new VProperty(TEXT("X-FUNAMBOL-ANNIVERSARY"));
        vp->addValue(CFString2Std(value).c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
        CFRelease(value);
    }

    /*
    // TODO No support
    if (getProperty("Children", element)) {
        vp = new VProperty("X-FUNAMBOL-CHILDREN", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No support
    if (getProperty("Companies", element)) {
        vp = new VProperty("X-FUNAMBOL-COMPANIES", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No support
    if (getProperty("Folder", element)) {
        vp = new VProperty("X-FUNAMBOL-FOLDER");
        vp->addValue(element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No support
    if (getProperty("Gender", element)) {
        vp = new VProperty("X-FUNAMBOL-GENDER", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No support
    if (getProperty("Hobby", element)) {
        vp = new VProperty("X-FUNAMBOL-HOBBIES", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    if (getProperty("IMAddress", element)) {
        vp = new VProperty("EMAIL", element.c_str());
        vp->addParameter("INTERNET", NULL);
        vp->addParameter("HOME",     NULL);
        vp->addParameter("X-FUNAMBOL-INSTANTMESSENGER", NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("Initials", element)) {
        vp = new VProperty("X-FUNAMBOL-INITIALS", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("Language", element)) {
        vp = new VProperty("X-FUNAMBOL-LANGUAGES", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("ManagerName", element)) {
        vp = new VProperty("X-FUNAMBOL-MANAGER", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("Mileage", element)) {
        vp = new VProperty("X-FUNAMBOL-MILEAGE", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("RadioTelephoneNumber", element)) {
        vp = new VProperty("TEL", element.c_str());
        vp->addParameter("X-FUNAMBOL-RADIO", NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support    
    if (getProperty("Spouse", element)) {
        vp = new VProperty("X-FUNAMBOL-SPOUSE", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("Subject", element)) {
        vp = new VProperty("X-FUNAMBOL-SUBJECT", element.c_str());
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */
    /*
    // TODO No Support
    if (getProperty("TelexNumber", element)) {
        vp = new VProperty("TEL", element.c_str());
        vp->addParameter("X-FUNAMBOL-TELEX", NULL);
        vo->addProperty(vp);
        delete vp; vp = NULL;
    }
    */


    vp = new VProperty(TEXT("END"), TEXT("VCARD"));
    vo->addProperty(vp);
    delete vp; vp = NULL;


    //
    // Format the vCard.
    // -----------------
    //
    char * tmp = vo->toString();
    if (tmp) {
        vcardString = tmp;
        delete [] tmp;
    }
    delete vo;
}



@end
